package com.such.kit.io.template;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import org.apache.commons.collections4.multimap.ArrayListValuedHashMap;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.ElementHandler;
import org.dom4j.ElementPath;
import org.dom4j.Namespace;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
import com.alibaba.fastjson.JSON;
import com.such.kit.Callable;
import com.such.kit.Logger;
import com.such.kit.datacast.CastCommons;
import com.such.kit.el.ExpressionCommons;
import com.such.kit.file.FileCommons;
import com.such.kit.io.simple.support.IOSimple;
import com.such.kit.io.source.DatabaseIOSource;
import com.such.kit.io.support.IOSource;
import com.such.kit.io.template.support.IOTemplate;
import com.such.kit.placeholder.PlaceholderCommons;
import com.such.kit.stream.StreamCommons;
import com.such.kit.string.StringCommons;
import com.such.kit.validate.ValidateCommons;
import com.such.kit.validate.validator.Validator;
import com.such.kit.validate.validator.bean.VerifyResult;

/**
 * @author SUCH
 * <pre>
 * [继承扩展于 {@link IOTemplate}] 的 xml 类型 IO 工具类
 * 
 * XmlIOTemplate 的模板介质为 xml 文件
 * XmlIOTemplate 的模板格式基于 xml 扩展，扩展项为 {@link #IO_INPUT_EXPRESSION}、{@link #IO_OUTPUT_EXPRESSION}、{@link #IO_OUTPUT_FILTER}
 * 待取值节点需要使用 {@link PlaceholderCommons 占位符}
 *   [导入时] 数据文件节点会与模板文件节点相对应，#[tableName] 会提取出 tableName 作 key，生成数据 {tableName: value}
 *   [导入时] ioSource.doInput 的 extraParams 有：{@link #IO_INPUT_EXTRA_PARAM_ID}、{@link #IO_INPUT_EXTRA_PARAM_PARENT_ID}
 *   [导出时] 数据可以被待取值节点中的 {@link PlaceholderCommons 占位符} 引用，最终输出值
 *   [导出时] {@link #IO_OUTPUT_FILTER} 可以一定程度的实现节点动态过滤
 * 
 * [举例] 以 {@link DatabaseIOSource} 为类
 * {@code
 * <?xml version="1.0" encoding="UTF-8"?>
 * <database dbType="#[dbType]">
 *   <tables>
 *     <table 
 *         schema="#[schema]" 
 *         tableName="#[tableName]" 
 *         tableType="#[tableType]" 
 *         tableComment="#[tableComment]" 
 *         IO_INPUT_EXPRESSION="insert into TABLE_INFO (schema, name, comment) value (:schema, :tableName, :tableComment)" 
 *         IO_OUTPUT_EXPRESSION="select schema as schema, name as tableName, comment as tableComment from TABLE_INFO"
 *     >
 *       <columns>
 *         <column 
 *             schema="#[schema]" 
 *             tableName="#[tableName]" 
 *             columnName="#[columnName]" 
 *             dataType="#[dataType]" 
 *             columnSize="#[columnSize]" 
 *             decimalDigits="#[decimalDigits]" 
 *             defaultValue="#[defaultValue]" 
 *             notNull="#[notNull]" 
 *             autoincrement="#[autoincrement]" 
 *             columnComment="#[columnComment]" 
 *             IO_INPUT_EXPRESSION="insert into COLUMN_INFO (schema, tableName, columnName) value (:schema, :tableName, :columnName)" 
 *             IO_OUTPUT_EXPRESSION="
 *                 select schema, tableName, columnName, dataType, columnSize, decimalDigits, defaultValue, notNull, autoincrement, columnComment 
 *                 from COLUMN_INFO 
 *                 where schema = :schema and tableName = :tableName"
 *         />
 *       </columns>
 *       <primaryKeys>
 *         <primaryKey 
 *             schema="#[schema]" 
 *             tableName="#[tableName]" 
 *             pkName="#[pkName]" 
 *             IO_OUTPUT_EXPRESSION="select distinct schema, tableName, pkName from PRIMARY_KEY_INFO where schema = :schema and table_name = :tableName"
 *         >
 *           <pkColumnNames>
 *             <pkColumnName 
 *                 IO_INPUT_EXPRESSION="insert into PRIMARY_KEY_INFO (schema, tableName, pkName, pkColumnName) value (:schema, :tableName, :pkName, :pkColumnName)" 
 *                 IO_OUTPUT_EXPRESSION="select pkColumnName from PRIMARY_KEY_INFO where schema = :schema and tableName = :tableName and pkName = :pkName"
 *             >#[pkColumnName]</pkColumnName>
 *           </pkColumnNames>
 *         </primaryKey>
 *       </primaryKeys>
 *     </table>
 *   </tables>
 * </database>
 * }
 * 表达式可以引用数据、{@link IOSimple#args 自定义参数集} 中的值，其中引用数据遵循以下规则
 * [导入时] {@link #IO_INPUT_EXPRESSION} 可在 xml 文档内引用 {@link #IO_INPUT_DEPTH} 深度的上下级节点、直属下级叶子节点中的数据
 * [导入时] [警告] 由于节点读取顺序的关系，只能引用当前节点之前的同级叶子节点
 * [导出时] {@link #IO_OUTPUT_EXPRESSION} 可在 xml 文档内引用所有上级节点、同级叶子节点中的数据，同时生成本节点以及下级节点
 * 
 * 当 [导出时] 生成了多个 root 节点，xml 会被 {@link #segmentable 拆分} 成多个 xml 并打包为 {@link #fileType zip}，zip 中条目名为一个 {@link #entryIndex 索引值}
 * </pre>
 */
public class XmlIOTemplate extends IOTemplate {

	public static final String FILE_TYPE_XML = "xml";
	public static final String FILE_TYPE_ZIP = "zip";
	/** IO_INPUT_EXTRA_PARAM_ID：[导入时] 扩展参数.ID */
	public static final String IO_INPUT_EXTRA_PARAM_ID = "id";
	/** IO_INPUT_EXTRA_PARAM_PARENT_ID：[导入时] 扩展参数.父级 ID */
	public static final String IO_INPUT_EXTRA_PARAM_PARENT_ID = "parentId";
	/**
	 * IO_INPUT_EXPRESSION：[导入时] 表达式
	 * <pre>
	 * {@link IOSource} 的不同实现 IO_INPUT_EXPRESSION 的推荐值有所变化
	 * 以 {@link DatabaseIOSource} 为例，IO_INPUT_EXPRESSION 的推荐值为 sql 语句
	 * [举例] ["insert into TABLE_INFO (schema, name, comment) value (:schema, :tableName, :tableComment)"]
	 * </pre>
	 */
	public static final String IO_INPUT_EXPRESSION = "IO_INPUT_EXPRESSION";
	/**
	 * IO_INPUT_DEPTH：[导入时] 指定节点引用上下级节点的深度 [默认为 0]
	 * <pre>
	 * -n 表示引用上 n 级级节点
	 * .
	 * .
	 * .
	 * -2 表示引用上 2 级节点
	 * -1 表示引用上 1 级节点
	 * 0 表示不引用
	 * 1 表示下级被上 1 级节点
	 * 2 表示下级被上 2 级节点
	 * .
	 * .
	 * .
	 * n 表示下级被上 n 级节点
	 * </pre>
	 */
	public static final String IO_INPUT_DEPTH = "IO_INPUT_DEPTH";
	/**
	 * IO_OUTPUT_EXPRESSION：[导出时] 表达式
	 * <pre>
	 * {@link IOSource} 的不同实现 IO_OUTPUT_EXPRESSION 的推荐值有所变化
	 * 以 {@link DatabaseIOSource} 为例，IO_OUTPUT_EXPRESSION 的推荐值为 sql 语句
	 * [举例] "select schema as schema, name as tableName, comment as tableComment from TABLE_INFO"
	 * </pre>
	 */
	public static final String IO_OUTPUT_EXPRESSION = "IO_OUTPUT_EXPRESSION";
	/**
	 * IO_OUTPUT_VNODE：[导出时] 虚拟节点
	 * <pre>
	 * IO_OUTPUT_VNODE 可以将一组原生节点包含在内
	 * 在 IO_OUTPUT_VNODE 上指定 IO_OUTPUT_EXPRESSION 达到使用一次循环输出多个同级的原生节点
	 * </pre>
	 */
	public static final String IO_OUTPUT_VNODE = "IO_OUTPUT_VNODE";
	/**
	 * IO_OUTPUT_FILTER：[导出时] 节点过滤器
	 * <pre>
	 * IO_OUTPUT_FILTER 可以使用简单的校验表达式动态过滤节点输出，做到一定程度的逻辑控制
	 * 每组表达式由 4 部分组成
	 *   组：组可以随意定义，推荐值 G1, G2 ... Gn，同组间以 or 连接，不同组间以 and 连接
	 *   校验器：检验器为 {@link Validator} 的实现类，可以是完整的长类名或者 com.such.kit.validate.validator.impl 包下的短类名（推荐）
	 *   待校验值：可以使用固定值或者引用值，引用规则可参考 {@link #IO_OUTPUT_EXPRESSION}
	 *   参数集：可接受的参数具体参考 {@link Validator} 的实现类，参数值可以是固定值或者引用值，引用规则可参考 {@link #IO_OUTPUT_EXPRESSION}
	 * 4 部分以满足 JSON 格式的数组组合 ["组", "校验器", "待校验值", "参数 1", "参数 2" ... "参数 n"]
	 * IO_OUTPUT_FILTER 支持多组表达式，以满足 JSON 格式的数组组合 [[表达式 1], [表达式 2] ...]
	 * IO_OUTPUT_FILTER 最终返回值为 true 则过滤节点
	 * 
	 * [警告] 由于需要在 xml 节点属性中编写表达式，表达式中的特殊符号需要使用转义符代替，较为常用的有 " > &quot;
	 * </pre>
	 */
	public static final String IO_OUTPUT_FILTER = "IO_OUTPUT_FILTER";
	/** IO_OUTPUT_FUNC：[导出时] 函数前缀 */
	public static final String IO_OUTPUT_FUNC = "IO_OUTPUT_FUNC:";

	private Document templateDocument;
	/** fileType：文件类型，当 segmentable 为 true，其值会被系统置为 {@link #FILE_TYPE_ZIP} [默认值为 {@link #FILE_TYPE_XML}] */
	private String fileType;
	/**
	 * segmentable：[导出时] 是否可拆分为多个 xml [true 为拆分 | false 为不拆分]
	 * <pre>
	 * 根据标准的 xml 规则，只能存在一个 root 节点
	 * 当由于某些原因，在 [导出时] 生成了多个 root 节点，segmentable 会被系统置为 true，此时系统会根据 root 节点拆分为多个 xml，最终打包为 zip 输出
	 * </pre>
	 */
	private boolean segmentable;
	/** filterEmptyNode：[导出时] 过滤空的节点 [true 过滤 | false 不过滤] [默认为 false] */
	private boolean filterEmptyNode = false;
	/**
	 * entryIndex：zip 中条目索引 [下标 1] [默认值为 1]
	 * <pre>
	 * 当 segmentable 为 true，系统会根据 root 节点拆分为多个 xml，最终打包为 zip 输出
	 * zip 中条目名为一个索引值，其从 1 开始，依次递增（+1），索引值会保持 6 位，不够位数则在前面补 0
	 * </pre>
	 */
	private int entryIndex = 1;

	/** [参考 {@link IOTemplate#IOTemplate(String)}] */
	public XmlIOTemplate(String templatePath) throws Exception {
		super(templatePath);
	}

	/** [参考 {@link IOTemplate#IOTemplate(String, String, IOSource)}] */
	public XmlIOTemplate(String templateString, String charset) throws Exception {
		super(templateString, charset);
	}

	/** [参考 {@link IOTemplate#IOTemplate(byte[], IOSource)}] */
	public XmlIOTemplate(byte[] templateBytes) throws Exception {
		super(templateBytes);
	}

	/** [参考 {@link IOTemplate#IOTemplate(InputStream, IOSource)}] */
	public XmlIOTemplate(InputStream templateIn) throws Exception {
		super(templateIn);
	}

	@Override
	protected void initTemplate() throws Exception {
		if (ValidateCommons.isEmpty(this.templateDocument)) {
			SAXReader reader = new SAXReader();
			this.templateDocument = reader.read(super.templateIn);
			String charset = this.templateDocument.getXMLEncoding();
			if (ValidateCommons.isNotEmpty(charset)) {
				super.charset = charset;
			}

			Element templateRoot = this.templateDocument.getRootElement();
			Attribute attribute = templateRoot.attribute(IO_OUTPUT_EXPRESSION);
			if (ValidateCommons.isNotEmpty(attribute)) {
				this.segmentable = true;
				this.fileType = FILE_TYPE_ZIP;
			} else {
				this.fileType = FILE_TYPE_XML;
			}
		}
	}

	@Override
	public void setCharset(String charset) {
		super.charset = charset;
	}

	@Override
	public void putArg(String key, Object value) {
		super.args.put(key, value);
	}

	@Override
	public void putArgs(Map<String, Object> args) {
		super.args.putAll(args);
	}

	@Override
	public void clearArgs() {
		super.args.clear();
	}

	@Override
	public String getFileType() {
		return this.fileType;
	}

	/**
	 * <pre>
	 * 强制指定 {@link #segmentable}
	 * </pre>
	 * @param segmentable [参考 {@link #segmentable}]
	 */
	public void setSegmentable(boolean segmentable) {
		this.segmentable = segmentable;
	}

	/**
	 * <pre>
	 * 指定 {@link #filterEmptyNode}
	 * </pre>
	 * @param filterEmptyNode [参考 {@link #filterEmptyNode}]
	 */
	public void setFilterEmptyNode(boolean filterEmptyNode) {
		this.filterEmptyNode = filterEmptyNode;
	}

	@Override
	public void input(IOSource ioSource, InputStream resourceIn) throws Exception {
		SAXReader reader = new SAXReader();
		reader.setDefaultHandler(new IOElementHandler(ioSource));
		reader.read(new InputStreamReader(resourceIn, super.charset));
	}

	private List<Namespace> getNamespaces(Element element) {
		/*
		 * declaredNamespaces() 在某些运行环境下无法获取到无前缀（默认）的 Namespace（xmlns）
		 * 因此使用 getNamespace() + additionalNamespaces() 实现
		 */
		List<Namespace> namespaces = new ArrayList<Namespace>();
		boolean isRoot = element.isRootElement();
		if (isRoot) {
			Namespace namespace = element.getNamespace();
			if (ValidateCommons.isNotEmpty(namespace) && ValidateCommons.isNotEmpty(namespace.getURI())) {
				namespaces.add(namespace);
			}
			List<Namespace> additionalNamespaces = element.additionalNamespaces();
			if (ValidateCommons.isNotEmpty(additionalNamespaces)) {
				namespaces.addAll(additionalNamespaces);
			}
		}
		return namespaces;
	}

	private class IOElementHandler implements ElementHandler {

		private Map<String, Map<String, Object>> levelFieldValueStorer = new HashMap<String, Map<String,Object>>();
		private Map<String, String> levelExpressionStorer = new HashMap<String, String>();
		private Map<String, String> levelIdStorer = new HashMap<String, String>();
		private Map<String, Integer> levelDepthStorer = new HashMap<String, Integer>();
		private IOSource ioSource;

		public IOElementHandler(IOSource ioSource) {
			this.ioSource = ioSource;
		}

		@Override
		public void onStart(ElementPath ep) {
			Element resourceElement = ep.getCurrent();
			String resourcePath = resourceElement.getPath();
			Node templateNode = templateDocument.selectSingleNode(resourcePath);
			if (ValidateCommons.isNotEmpty(templateNode)) {
				Element templateElement = (Element) templateNode;
				String path = templateElement.getPath();

				/*
				 * 注意：onStart 中只能获取节点属性，无法获取节点中内容（文本）
				 * 
				 * 遇到 IO_INPUT_EXPRESSION 节点则开始准备数据：
				 * 1. 准备初始化数据，由于有模板文件中存在，而数据文件中不存在的节点，因此需要以模板文件为标准初始化所有字段（空值）
				 * 2. 初始化范围需要满足以下条件：
				 * 		1) 直属的下级叶子（可以通过 isTextOnly 判断）节点
				 * 		2) 满足条件 1) 的节点中无 IO_INPUT_EXPRESSION 属性
				 */
				Attribute ieAttribute = templateElement.attribute(IO_INPUT_EXPRESSION);
				if (ValidateCommons.isNotEmpty(ieAttribute)) {
					String ie = ieAttribute.getValue();
					this.levelExpressionStorer.put(path, ie);
					this.levelIdStorer.put(path, UUID.randomUUID().toString());
					List<Element> templateChilds = templateElement.elements();
					if (ValidateCommons.isNotEmpty(templateChilds)) {
						for (Element templateChild : templateChilds) {
							if (templateChild.isTextOnly() && ValidateCommons.isEmpty(templateChild.attribute(IO_INPUT_EXPRESSION))) {
								buildParams(templateChild, null);
							}
						}
					}
				}

				Attribute idAttribute = templateElement.attribute(IO_INPUT_DEPTH);
				if (ValidateCommons.isNotEmpty(idAttribute)) {
					String inputDepth = idAttribute.getValue();
					this.levelDepthStorer.put(path, CastCommons.stringToInteger(inputDepth));
				}

				buildParams(templateElement, resourceElement);
			}
		}

		@Override
		public void onEnd(ElementPath ep) {
			Element resourceElement = ep.getCurrent();
			String resourcePath = resourceElement.getPath();
			Node templateNode = templateDocument.selectSingleNode(resourcePath);
			if (ValidateCommons.isNotEmpty(templateNode)) {
				Element templateElement = (Element) templateNode;

				buildParams(templateElement, resourceElement);

				Attribute ieAttribute = templateElement.attribute(IO_INPUT_EXPRESSION);
				if (ValidateCommons.isNotEmpty(ieAttribute)) {
					String ie = ieAttribute.getValue();
					String path = templateElement.getPath();
					Integer inputDepth = this.levelDepthStorer.get(path);
					// 表达式通常会依赖上层数据构建，在此指定一个特大深度，保证可以引用到所有上级节点
					if (ValidateCommons.isNotEmpty(inputDepth)) {
						Map<String, Object> fieldValueMap = getFieldValueMap(resourcePath, -999);
						ie = PlaceholderCommons.replacePlaceholder(ie, fieldValueMap);
					}
					Map<String, Object> extraParams = new HashMap<String, Object>();
					fillId(path, extraParams);
					this.ioSource.doInput(ie, getFieldValueMap(resourcePath, inputDepth), false, extraParams);
				}
				if (templateElement.isRootElement()) {
					// 当完成 XML 文档处理后，执行剩余的所有语句
					this.ioSource.doInput(null, null, true, null);
				}
			}
			resourceElement.detach(); // 从 DOM 树上删除节点
		}

		private void buildParams(Element templateElement, Element resourceElement) {
			String path = templateElement.getPath();
			/*
			 * 判断 templateElement 是否应将数据保存在上级路径中：
			 * 1. templateElement 中无 IO_INPUT_EXPRESSION 属性
			 * 2. 引用下级节点深度
			 */
			if (ValidateCommons.isEmpty(templateElement.attribute(IO_INPUT_EXPRESSION))) {
				path = FileCommons.cutLastPathNode(path);
				Integer inputDepth = this.levelDepthStorer.get(path);
				if (ValidateCommons.isNotEmpty(inputDepth)) {
					// 处理下级被上级节点引用
					int cutCount = inputDepth;
					while (ValidateCommons.isNotEmpty(path) && cutCount > 0) {
						// 使用路径剪切进行深度引用
						path = FileCommons.cutLastPathNode(path);
						cutCount--;
					}
				}
			}

			List<Namespace> namespaces = getNamespaces(templateElement);
			if (ValidateCommons.isNotEmpty(namespaces) && ValidateCommons.isNotEmpty(resourceElement)) {
				for (Namespace namespace : namespaces) {
					String prefix = namespace.getPrefix();
					String uri = namespace.getURI();
					String key = PlaceholderCommons.getInnerKey(uri);
					if (ValidateCommons.isNotEmpty(key)) {
						String value = "";
						Namespace resourceNamespace = resourceElement.getNamespaceForPrefix(prefix);
						if (ValidateCommons.isNotEmpty(resourceNamespace)) {
							value = resourceNamespace.getURI();
							if (ValidateCommons.isEmpty(value)) {
								value = "";
							}
						}
						putFieldValue(path, key, value);
					}
				}
			}
			List<Attribute> templateAttributes = templateElement.attributes();
			if (ValidateCommons.isNotEmpty(templateAttributes)) {
				for (Attribute templateAttribute : templateAttributes) {
					String attrName = templateAttribute.getName();
					// 跳过自定义节点
					if (IO_INPUT_EXPRESSION.equals(attrName) || 
							IO_INPUT_DEPTH.equals(attrName) || 
							IO_OUTPUT_EXPRESSION.equals(attrName) || 
							IO_OUTPUT_FILTER.equals(attrName)) {
						continue;
					}
					String innerKey = templateAttribute.getValue();
					String key = PlaceholderCommons.getInnerKey(innerKey);
					if (ValidateCommons.isNotEmpty(key)) {
						String value = "";
						if (ValidateCommons.isNotEmpty(resourceElement)) {
							value = resourceElement.attributeValue(attrName);
							if (ValidateCommons.isEmpty(value)) {
								value = "";
							}
						}
						putFieldValue(path, key, value);
					}
				}
			}
			if (templateElement.isTextOnly()) {
				String innerKey = templateElement.getTextTrim();
				String key = PlaceholderCommons.getInnerKey(innerKey);
				if (ValidateCommons.isNotEmpty(key)) {
					String value = "";
					if (ValidateCommons.isNotEmpty(resourceElement)) {
						value = resourceElement.getTextTrim();
						if (ValidateCommons.isEmpty(value)) {
							value = "";
						}
					}
					putFieldValue(path, key, value);
				}
			}
		}

		private void putFieldValue(String path, String key, String value) {
			Map<String, Object>  fieldValueMap = this.levelFieldValueStorer.get(path);
			if (fieldValueMap == null) {
				fieldValueMap = new HashMap<String, Object>();
			}
			fieldValueMap.put(key, value);
			this.levelFieldValueStorer.put(path, fieldValueMap);
		}

		private Map<String, Object> getFieldValueMap(String path, Integer inputDepth) {
			Map<String, Object> fieldValueMap = new HashMap<String, Object>(args);
			if (ValidateCommons.isEmpty(inputDepth)) {
				inputDepth = 0;
			}
			// 处理引用上级节点
			int cutCount = inputDepth;
			while (ValidateCommons.isNotEmpty(path) && cutCount <= 0) {
				Map<String, Object> depthFieldValueMap = this.levelFieldValueStorer.get(path);
				if (ValidateCommons.isNotEmpty(depthFieldValueMap)) {
					fieldValueMap.putAll(depthFieldValueMap);
				}
				// 使用路径剪切进行深度引用
				path = FileCommons.cutLastPathNode(path);
				cutCount++;
			}
			return fieldValueMap;
		}

		private void fillId(String path, Map<String, Object> params) {
			String parentId = null;
			String parentPath = FileCommons.cutLastPathNode(path);
			while (ValidateCommons.isNotEmpty(parentPath) && ValidateCommons.isEmpty(parentId)) {
				parentId = this.levelIdStorer.get(parentPath);
				parentPath = FileCommons.cutLastPathNode(parentPath);
			}
			params.put(IO_INPUT_EXTRA_PARAM_ID, this.levelIdStorer.get(path));
			params.put(IO_INPUT_EXTRA_PARAM_PARENT_ID, parentId);
		}
	}

	private ExpressionCommons expressionCommons = null;
	public <T> void registerOutputFunction(Class<T> clazz) {
		if (ValidateCommons.isEmpty(this.expressionCommons)) {
			this.expressionCommons = ExpressionCommons.getInstance(ExpressionCommons.ENGINE_TYPE_MVEL);
		}
		this.expressionCommons.registers(clazz);
	}

	@Override
	public void output(IOSource ioSource, OutputStream resourceOut) throws Exception {
		Element templateRoot = this.templateDocument.getRootElement();
		if (this.segmentable) {
			ZipOutputStream zipOut = null;
			try {
				zipOut = new ZipOutputStream(resourceOut, Charset.forName(this.charset));
				outputNode(ioSource, templateRoot, null, zipOut, 0);
				zipOut.flush();
			} finally {
				StreamCommons.close(zipOut);
			}
		} else {
			outputNode(ioSource, templateRoot, null, resourceOut, 0);
		}
	}

	private void outputNode(IOSource ioSource, Element templateElement, Map<String, Object> params, OutputStream out, int level) throws Exception {
		if (ValidateCommons.isEmpty(params)) {
			params = new HashMap<String, Object>();
		}
		params.putAll(super.args);

		Attribute oeAttribute = templateElement.attribute(IO_OUTPUT_EXPRESSION);
		if (ValidateCommons.isNotEmpty(oeAttribute)) {
			String oe = oeAttribute.getValue();

			ioSource.doOutput(oe, params, new OutputCallable(ioSource, templateElement, out, level));
		} else {
			appendNode(ioSource, templateElement, params, out, level);
		}
	}

	private void appendNode(IOSource ioSource, Element templateElement, Map<String, Object> params, OutputStream out, int level) throws Exception {
		// 检查节点是否需要过滤
		boolean filterable = doFilter(templateElement, params);
		if (filterable) {
			return;
		}
		boolean isRoot = templateElement.isRootElement();
		try {
			if (this.segmentable && isRoot) {
				ZipEntry zipEntry = new ZipEntry(StringCommons.merger(CastCommons.numberToString(this.entryIndex, 6), ".xml"));
				((ZipOutputStream) out).putNextEntry(zipEntry);
			}
			if (isRoot) {
				out.write(StringCommons.merger("<?xml version=\"1.0\" encoding=\"", super.charset, "\"?>").getBytes(super.charset));
			}
			String xmlFragment = buildNode(ioSource, templateElement, params, level);
			if (ValidateCommons.isNotEmpty(xmlFragment)) {
				out.write(xmlFragment.getBytes(super.charset));
			}
		} finally {
			if (this.segmentable && isRoot) {
				((ZipOutputStream) out).closeEntry();
				this.entryIndex++;
			}
		}
	}

	private String buildNode(IOSource ioSource, Element templateElement, Map<String, Object> params, int level) throws Exception {
		boolean emptyNode = true;
		String format = StringCommons.merger(StringCommons.NEW_LINE, StringCommons.repeat(StringCommons.RETRACT, level));
		String namespacePrefix = templateElement.getNamespacePrefix();
		String nodeName = templateElement.getName();
		nodeName = PlaceholderCommons.replacePlaceholder(nodeName, params);
		if (ValidateCommons.isNotEmpty(namespacePrefix)) {
			nodeName = StringCommons.merger(namespacePrefix, ":", nodeName);
		}

		StringBuffer xmlFragment = new StringBuffer();
		xmlFragment.append(format);
		xmlFragment.append('<');
		xmlFragment.append(nodeName);
		List<Namespace> namespaces = getNamespaces(templateElement);
		if (ValidateCommons.isNotEmpty(namespaces)) {
			for (Namespace namespace : namespaces) {
				String xml = PlaceholderCommons.replacePlaceholder(namespace.asXML(), params);
				xmlFragment.append(' ');
				xmlFragment.append(xml);
			}
		}
		List<Attribute> templateAttrs = templateElement.attributes();
		if (ValidateCommons.isNotEmpty(templateAttrs)) {
			for (Attribute templateAttr : templateAttrs) {
				String attrName = templateAttr.getName();
				// 跳过自定义节点
				if (IO_INPUT_EXPRESSION.equals(attrName) || 
						IO_INPUT_DEPTH.equals(attrName) || 
						IO_OUTPUT_EXPRESSION.equals(attrName) || 
						IO_OUTPUT_FILTER.equals(attrName)) {
					continue;
				}
				String attrValue = templateAttr.getValue();
				attrValue = PlaceholderCommons.replacePlaceholder(attrValue, params);
				// 执行表达式
				if (ValidateCommons.isNotEmpty(this.expressionCommons) && attrValue.startsWith(IO_OUTPUT_FUNC)) {
					attrValue = attrValue.substring(IO_OUTPUT_FUNC.length());
					attrValue = this.expressionCommons.execute(attrValue, null);
				}
				xmlFragment.append(' ');
				xmlFragment.append(attrName);
				xmlFragment.append('=');
				xmlFragment.append('"');
				xmlFragment.append(attrValue);
				xmlFragment.append('"');
				if (emptyNode && ValidateCommons.isNotEmpty(attrValue)) {
					emptyNode = false;
				}
			}
		}
		xmlFragment.append('>');
		if (templateElement.isTextOnly()) {
			String text = templateElement.getTextTrim();
			text = PlaceholderCommons.replacePlaceholder(text, params);
			// 执行表达式
			if (ValidateCommons.isNotEmpty(this.expressionCommons) && text.startsWith(IO_OUTPUT_FUNC)) {
				text = text.substring(IO_OUTPUT_FUNC.length());
				text = this.expressionCommons.execute(text, null);
			}
			if (emptyNode && ValidateCommons.isNotEmpty(text)) {
				emptyNode = false;
			}
			xmlFragment.append(text);
		} else {
			List<Element> templateChilds = templateElement.elements();
			if (ValidateCommons.isNotEmpty(templateChilds)) {
				for (Element templateChild : templateChilds) {
					ByteArrayOutputStream out = new ByteArrayOutputStream();
					outputNode(ioSource, templateChild, params, out, level + 1);
					if (emptyNode && out.size() > 0) {
						emptyNode = false;
					}
					xmlFragment.append(new String(out.toByteArray(), super.charset));
				}
			}
			xmlFragment.append(format);
		}
		xmlFragment.append('<');
		xmlFragment.append('/');
		xmlFragment.append(nodeName);
		xmlFragment.append('>');
		if (!this.filterEmptyNode || (this.filterEmptyNode && !emptyNode)) {
			return xmlFragment.toString();
		} else {
			return "";
		}
	}

	private boolean doFilter(Element templateElement, Map<String, Object> params) {
		boolean filterable = false;
		Attribute ofAttribute = templateElement.attribute(IO_OUTPUT_FILTER);
		if (ValidateCommons.isNotEmpty(ofAttribute)) {
			String nodeName = templateElement.getName();
			try {
				List<List> ofList = JSON.parseArray(ofAttribute.getValue(), List.class);
				if (ValidateCommons.isNotEmpty(ofList)) {
					ArrayListValuedHashMap<String, Boolean> checkerMap = new ArrayListValuedHashMap<String, Boolean>();
					for (int i = 0; i < ofList.size(); i++) {
						try {
							List<String> of = ofList.get(i);
							if (of.size() < 3) {
								Logger.warn(getClass(), StringCommons.merger("跳过节点 [", nodeName, "] 第 [", i + 1, "] 组 output 过滤表达式，其组成部分不完整"));
								continue;
							}
							String group = of.remove(0);
							String validator = of.remove(0);
							String value = PlaceholderCommons.replacePlaceholder(of.remove(0), params);
							List<String> args = new ArrayList<String>();
							if (ValidateCommons.isNotEmpty(of)) {
								for (String arg : of) {
									args.add(PlaceholderCommons.replacePlaceholder(arg, params));
								}
							}
							VerifyResult vr = null;
							try {
								vr = ValidateCommons.validator(StringCommons.merger("com.such.kit.validate.validator.impl.", validator), value, args);
							} catch (Exception e) {
								vr = ValidateCommons.validator(validator, value, args);
							}
							checkerMap.put(group, vr.getResult());
						} catch (Exception e) {
							Logger.error(getClass(), StringCommons.merger("节点 [", nodeName, "] 第 [", i + 1, "] 组 output 过滤表达式编译或执行失败"), e);
						}
					}
					for (String group : checkerMap.keySet()) {
						List<Boolean> booles = checkerMap.get(group);
						boolean checker = false;
						for (boolean boole : booles) {
							checker = boole;
							if (checker) {
								break;
							}
						}
						filterable = checker;
						if (!filterable) {
							break;
						}
					}
				}
			} catch (Exception e) {
				Logger.error(getClass(), StringCommons.merger("节点 [", nodeName, "] 编译 output 过滤表达式失败，默认返回 false（不过滤）"), e);
			}
		}
		return filterable;
	}

	private class OutputCallable implements Callable<Map<String, Object>, Boolean> {

		private IOSource ioSource;
		private Element templateElement;
		private OutputStream out;
		private int level;

		public OutputCallable(IOSource ioSource, Element templateElement, OutputStream out, int level) {
			this.ioSource = ioSource;
			this.templateElement = templateElement;
			this.out = out;
			this.level = level;
		}

		@Override
		public Boolean call(Map<String, Object> params) throws Exception {
			String name = this.templateElement.getName();
			// 处理虚拟节点输出
			if (IO_OUTPUT_VNODE.equals(name)) {
				List<Element> templateChilds = this.templateElement.elements();
				if (ValidateCommons.isNotEmpty(templateChilds)) {
					for (Element templateChild : templateChilds) {
						appendNode(this.ioSource, templateChild, params, this.out, this.level);
					}
				}
			} else {
				appendNode(this.ioSource, this.templateElement, params, this.out, this.level);
			}
			return true;
		}

	}

}
